#ifndef __ZHONG_ELASTIC__
#define __ZHONG_ELASTIC__
#include "logger.hpp"

#include <elasticlient/client.h>
#include <cpr/cpr.h>
#include <json/json.h>
#include <memory>
#include <vector>
#include <exception>
#include <iostream>
#include <string>
#include <sstream>
// 文件说明注释
/*
封装客户端 api 主要是因为，客户端只提供了基础的数据存储获取调用功能，
无法根据我们的思想完成索引的构建，以及查询正文的构建，需要使用者自己组织好 json 进行序列化后才能作为正文进行接口的调用。
而封装的目的就是简化用户的操作，将索引的 json 正文构造，以及查询搜索的正文构造操作给封装起来，
使用者调用接口添加字段就行，不用关心具体的 json 数据格式。
封装内容：
    • 索引构造过程的封装
    索引正文构造过程，大部分正文都是固定的，唯一不同的地方是各个字段不同的名称以及是否只存储不索引这些选项，因此重点关注以下几个点即可：
    • 字段类型：type : text / keyword (目前只用到这两个类型)
    • 是否索引：enable : true/false
    • 索引的话分词器类型： analyzer : ik_max_word / standard
    • 新增文档构造过程的封装
￮ 新增文档其实在常规下都是单条新增，并非批量新增，因此直接添加字段和值就行
    • 文档搜索构造过程的封装
    搜索正文构造过程，我们默认使用条件搜索，我们主要关注的两个点：
    • 应该遵循的条件是什么：should 中有什么
    • 条件的匹配方式是什么：match 还是 term/terms，还是 wildcard
    • 过滤的条件字段是什么：must_not 中有什么
    • 过滤的条件字段匹配方式是什么：match 还是 wildcard，还是 term/terms
整个封装的过程其实就是对 Json::Value 对象的一个组织的过程，并无太大的难点
*/

// namespace zhong
// {
//     // 根据es的JSON结构通过elasticlient客户端来实现es数据存储的操作
//     namespace elasticer
//     {

//     };
// };

namespace bite_im
{
    // 管理elasticlient::Client的shared_ptr
    using ElasticPtr = std::shared_ptr<elasticlient::Client>;
    // 将json格式序列化为字符串
    bool Serialize(const Json::Value &val, std::string &dest)
    {
        // 先构造一个工厂类
        Json::StreamWriterBuilder swb;
        // 获取一个writer对象
        std::unique_ptr<Json::StreamWriter> writer(swb.newStreamWriter());
        std::stringstream ss;
        int ret = writer->write(val, &ss);
        if (ret != 0)
        {
            return false;
        }
        dest = ss.str();
        return true;
    }
    // 字符串格式反序列化json
    bool Deserialize(Json::Value &val, const std::string &src)
    {
        // 先构造一个工厂类
        Json::CharReaderBuilder crb;
        // 获取一个reader对象
        std::unique_ptr<Json::CharReader> reader(crb.newCharReader());
        std::string err;
        int ret = reader->parse(src.c_str(), src.c_str() + src.size(), &val, &err);
        if (ret == false)
        {
            return false;
        }
        return true;
    }

    // 创建es数据存储的索引
    class ESIndex
    {
    public:
        // 这里做了添加const ElasticPtr的修改
        ESIndex(const ElasticPtr &client, const std::string &index_name, const std::string &doc_type = "_doc")
            : _client(client), _index_name(index_name), _doc_type(doc_type)
        {
            Json::Value analysis;
            Json::Value analyzer;
            Json::Value ik;
            Json::Value tokenizer;
            tokenizer["tokenizer"] = "ik_max_word";
            ik["ik"] = tokenizer;
            analyzer["analyzer"] = ik;
            analysis["analysis"] = analyzer;
            _index["settings"] = analysis;
        }
        // 添加新的key
        ESIndex &append(const std::string &key,
                        const std::string &key_type = "text",
                        const std::string &analyzer_type = "ik_max_word",
                        bool enabled = true)
        {
            Json::Value fields;

            fields["type"] = key_type;
            fields["analyzer"] = analyzer_type;
            if (enabled == false)
            {
                fields["enabled"] = enabled;
            }
            _properties[key] = fields;
            return *this;
        }
        // 创建索引
        bool create(const std::string &id = "default_index_id")
        {
            Json::Value mappings;
            mappings["dynamic"] = true;
            mappings["properties"] = _properties;
            _index["mappings"] = mappings;

            std::string body;
            bool ret = Serialize(_index, body);
            if (ret == false)
            {
                LOG_ERROR("索引序列化失败");
                return false;
            }
            LOG_DEBUG("\n请求正文为：\n{}", body);
            // 发起创建索引
            try
            {
                auto resp = _client->index(_index_name, _doc_type, id, body);
                if (resp.status_code < 200 || resp.status_code >= 300)
                {
                    LOG_ERROR("创建ES索引{}失败，响应状态码异常[{}]", _index_name, resp.status_code);
                    return false;
                }
            }
            catch (std::exception &e)
            {
                LOG_ERROR("创建ES索引{}失败，原因[{}]", _index_name, e.what());
                return false;
            }
            LOG_DEBUG("索引创建成功");
            return true;
        }

    private:
        std::string _index_name;
        std::string _doc_type;
        Json::Value _index;
        Json::Value _properties;
        ElasticPtr _client;
    };

    // 进行ES数据的新增
    class ESInsert
    {
    public:
        ESInsert(const ElasticPtr &client, const std::string &index_name, const std::string &doc_type = "_doc")
            : _client(client), _index_name(index_name), _doc_type(doc_type) {}

        // 添加新的字段
        template <typename T>
        ESInsert &append(const std::string &key, const T &value)
        {
            _item[key] = value;
            return *this;
        }
        bool insert(const std::string &id = "")
        {
            std::string body;
            bool ret = Serialize(_item, body);
            if (ret == false)
            {
                LOG_ERROR("新增数据序列化失败");
                return false;
            }
            LOG_DEBUG("\n请求正文为：\n{}", body);
            try
            {
                auto resp = _client->index(_index_name, _doc_type, id, body);
                if (resp.status_code < 200 || resp.status_code >= 300)
                {
                    LOG_ERROR("新增数据{}失败，响应状态码异常[{}]", body, resp.status_code);
                    return false;
                }
            }
            catch (std::exception &e)
            {
                LOG_ERROR("新增数据{}失败，原因[{}]", body, e.what());
                return false;
            }
            LOG_DEBUG("新增数据成功");
            return true;
        }

    private:
        std::string _index_name;
        std::string _doc_type;
        Json::Value _item;
        ElasticPtr _client;
    };
    // 进行ES数据的删除
    class ESRemove
    {
    public:
        ESRemove(const ElasticPtr &client, const std::string &index_name, const std::string &doc_type = "_doc")
            : _client(client), _index_name(index_name), _doc_type(doc_type) {}
        bool remove(const std::string &id)
        {
            try
            {
                auto resp = _client->remove(_index_name, _doc_type, id);
                if (resp.status_code < 200 || resp.status_code >= 300)
                {
                    LOG_ERROR("删除数据{}失败，响应状态码异常[{}]", id, resp.status_code);
                    return false;
                }
            }
            catch (std::exception &e)
            {
                LOG_ERROR("删除数据{}失败，原因[{}]", id, e.what());
                return false;
            }
            LOG_DEBUG("删除数据成功");
            return true;
        }

    private:
        std::string _index_name;
        std::string _doc_type;
        ElasticPtr _client;
    };
    // 进行ES数据的搜索
    class ESSearch
    {
    public:
        ESSearch(const ElasticPtr &client, const std::string &index_name, const std::string &doc_type = "_doc")
            : _client(client), _index_name(index_name), _doc_type(doc_type) {}

        // 当前模块需结合 http://101.33.241.19:5601/app/dev_tools#/console 和 json语法 来编写
        // _must_not.append 对应创建[]
        // _must_not["_must_not"] 对应创建 [{}]

        // 增加必须不遵循的条件
        ESSearch &append_must_not_terms(const std::string &key, const std::vector<std::string> &values)
        {
            Json::Value field;
            Json::Value terms;
            for (auto &value : values)
            {
                field[key].append(value);
            }
            terms["terms"] = field;
            _must_not.append(terms);
            return *this;
        }
        // 增加必须遵循的条件
        ESSearch &append_must_term(const std::string &key, const std::string &value)
        {
            Json::Value field;
            Json::Value term;
            field[key] = value;
            term["term"] = field;
            _must.append(term);
            return *this;
        }
        ESSearch &append_must_match(const std::string &key, const std::string &value)
        {
            Json::Value field;
            Json::Value match;
            field[key] = value;
            match["match"] = field;
            _must.append(match);
            return *this;
        }
        // 增加遵循的条件
        ESSearch &append_should_match(const std::string &key, const std::string &value)
        {
            Json::Value field;
            Json::Value match;
            field[key] = value;
            match["match"] = field;
            _should.append(match);
            return *this;
        }
        Json::Value search()
        {
            // 1.根据search的内容构建json字段
            Json::Value cond;
            // 判断是否存在以下字段
            if (_must_not.empty() == false)
            {
                cond["must_not"] = _must_not;
            }
            if (_should.empty() == false)
            {
                cond["should"] = _should;
            }
            if (_must.empty() == false)
            {
                cond["must"] = _must;
            }
            Json::Value query;
            query["bool"] = cond;
            Json::Value root;
            root["query"] = query;

            std::string body;
            bool ret = Serialize(root, body);
            if (ret == false)
            {
                LOG_ERROR("索引序列化失败！");
                return Json::Value();
            }
            LOG_DEBUG("\n请求正文为：\n{}", body);
            // 2. 发起搜索请求
            cpr::Response resp;
            try
            {
                resp = _client->search(_index_name, _doc_type, body);
                if (resp.status_code < 200 || resp.status_code >= 300)
                {
                    LOG_ERROR("检索数据 {} 失败，响应状态码异常: {}", body, resp.status_code);
                    return Json::Value();
                }
            }
            catch (std::exception &e)
            {
                LOG_ERROR("检索数据 {} 失败: {}", body, e.what());
                return Json::Value();
            }
            // 3. 需要对响应正文进行反序列化
            LOG_DEBUG("\n检索响应正文: \n{}", resp.text);
            // 存储反序列化后的json对象
            Json::Value ret_json;

            ret = Deserialize(ret_json, resp.text);
            if (ret == false)
            {
                LOG_ERROR("检索数据 {} 结果反序列化失败", resp.text);
                return Json::Value();
            }
            return ret_json["hits"]["hits"];
        }

    private:
        std::string _index_name;
        std::string _doc_type;
        Json::Value _must_not;
        Json::Value _must;
        Json::Value _should;
        ElasticPtr _client;
    };
}

#endif